/**
 * 
 */
package com.google.code.rookery;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

import com.google.code.rookery.converter.InstanceConverter;

/**
 * @author Xerox
 *
 */
public class FactoryTypeMapping implements Comparable<FactoryTypeMapping>{

  /**
   * Find any and all type mappings in the specified class
   * @param clazz the class being mapped
   * @return a collection of type mappings, which may be zero
   * @throws IllegalAccessException 
   * @throws InstantiationException 
   */
  public static FactoryTypeMapping[] findTypeMappings(Class<?> clazz) throws InstantiationException, IllegalAccessException{
    List<FactoryTypeMapping> mappings = new ArrayList<FactoryTypeMapping>();

    for(Annotation annotation:clazz.getAnnotations()){
      if(annotation instanceof FactoryProperty){
        mappings.add(new FactoryTypeMapping(clazz, (FactoryProperty) annotation));
      }
    }

    FactoryTypeMapping[] ret = new FactoryTypeMapping[mappings.size()];
    ret = mappings.toArray(ret);
    return ret;
  }

  private InstanceConverter[] converters;
  private String              propName;
  private Class<?>            targetType;

  /**
   * Create a mapping from the specified class type to the setter specified by the prop
   * @param clazz the to class type of the setter
   * @param prop the property being set.
   * @throws IllegalAccessException 
   * @throws InstantiationException 
   */
  protected FactoryTypeMapping(Class<?> clazz, FactoryProperty prop) throws InstantiationException, IllegalAccessException {
    this.targetType = clazz;
    this.propName = prop.name();
    Class<? extends InstanceConverter> converterClass = prop.converter();
    if(InstanceConverter.class.getName().equals(converterClass.getName())){
      //no converter, make empty list
      converters = new InstanceConverter[]{};
    }
    else{
      converters = new InstanceConverter[]{converterClass.newInstance()};
    }
  }

  /* (non-Javadoc)
   * @see java.lang.Comparable#compareTo(java.lang.Object)
   */
  @Override
  public int compareTo(FactoryTypeMapping other) {
    if (propName == null) {
      if (other.propName != null)
        return -1;
    }
    int ret = propName.compareTo(other.propName);
    if(ret!=0){
      return ret;
    }
    if (targetType == null) {
      if (other.targetType != null)
        return -1;
    }
    ret = targetType.getName().compareTo(other.targetType.getName());
    return ret;
  }

  /* (non-Javadoc)
   * @see java.lang.Object#equals(java.lang.Object)
   */
  @Override
  public boolean equals(Object obj) {
    if (this == obj)
      return true;
    if (obj == null)
      return false;
    if (getClass() != obj.getClass())
      return false;
    FactoryTypeMapping other = (FactoryTypeMapping) obj;
    
    return compareTo(other) == 0;
  }

  /* (non-Javadoc)
   * @see java.lang.Object#hashCode()
   */
  @Override
  public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + ((propName == null) ? 0 : propName.hashCode());
    result = prime * result + ((targetType == null) ? 0 : targetType.hashCode());
    return result;
  }

  /* (non-Javadoc)
   * @see java.lang.Object#toString()
   */
  @Override
  public String toString() {
    return "FactoryTypeMapping [propName=" + propName + ", targetType=" + targetType + "]";
  }

  /**
   * Set the value of the from object using the setter on the to object
   * @param from the object instance being stored
   * @param to the object with the setter for the from object
   * @param instanceConverters 
   * @throws InvocationTargetException 
   * @throws IllegalAccessException 
   * @throws IllegalArgumentException 
   */
  public void setValue(Object from, Object to) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException{
    Method setter = FactoryMethodMapping.findSetter(to.getClass(), propName);
    if(setter!=null){
      setter.invoke(to, new Object[]{from});
    }
  }

  /**
   * The array of instance converters
   * @return the instance converters for this mapping
   */
  public final InstanceConverter[] getConverters(){
    return converters;
  }

  /**
   * @return the propName
   */
  public final String getPropName() {
    return propName;
  }

  /**
   * @param propName the propName to set
   */
  public final void setPropName(String propName) {
    this.propName = propName;
  }

  /**
   * The class type of the setter being looked for
   * @return the targetType
   */
  public final Class<?> getTargetType() {
    return targetType;
  }

  /**
   * The class type of the setter being looked for
   * @param targetType the targetType to set
   */
  public final void setTargetType(Class<?> targetType) {
    this.targetType = targetType;
  }
}
